package DDBMapper.json;

import DDBMapper.*;
import com.amazonaws.services.dynamodb.model.AttributeValue;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;

import java.util.*;

public class JsonDDBConverter implements DDBConverter {

	@Override
	public <T> T convertTo(Map<String, AttributeValue> content, ClassManifest<T> manifest) throws DDBConverterException {
		
		T results;
		
		try{
			
			Map<String, Object> filtered = new HashMap<String, Object>();
			
			for (Map.Entry<String, AttributeValue> entry : content.entrySet()) {
				
				if (manifest.getTypeMap().containsKey(entry.getKey())) {
					
					DDBTypes keyType = manifest.getTypeMap().get(entry.getKey());
					if (keyType == DDBTypes.S) {
						
						filtered.put(entry.getKey(), entry.getValue().getS());
					}
					else if (keyType == DDBTypes.N) {
						
						filtered.put(entry.getKey(), entry.getValue().getN());
					}
					else if (keyType == DDBTypes.SS) {
						
						filtered.put(entry.getKey(), entry.getValue().getSS());
					}
					else if (keyType == DDBTypes.NS) {
						
						List<Integer> ns = new ArrayList<Integer>();
						
						for (String number : entry.getValue().getNS()) {
							
							//TODO: should check number can be parsed
							ns.add(Integer.parseInt(number));
						}
						
						filtered.put(entry.getKey(), ns);
					}
	 			}
			}
			
			ObjectMapper mapper = new ObjectMapper();
			byte[] json = mapper.writeValueAsBytes(filtered);
			results = mapper.readValue(json, manifest.getCastType());
			
		}catch(Exception e) {
			
			throw new DDBConverterException(e, "Cannot convert content json to type [%S].", manifest.getCastType().getName());
		}
		
		return results;
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> Map<String, AttributeValue> convertFrom(T content, ClassManifest<T> manifest) throws DDBConverterException {
		
		ObjectMapper mapper = new ObjectMapper();
		Map<String, Object> map;
		Map<String, AttributeValue> result = new HashMap<String, AttributeValue>(); 
		
		try {
			byte[] json = mapper.writeValueAsBytes(content);
			//TODO: there should be a check to see if the type field defined in T is the same as the definition in manifest
			map = mapper.readValue(json, new TypeReference<HashMap<String, Object>>(){});
		} catch (Exception e) {
			throw new DDBConverterException(e, "Cannot convert content of type [%S] to json.", manifest.getCastType().getName());
		}
		
		for (Map.Entry<String, Object> entry : map.entrySet()) {
			
			// skip those keys with null values and those that are not defined in the manifest			
			if (entry.getValue() != null && manifest.getTypeMap().containsKey(entry.getKey())) {
				
				DDBTypes type = manifest.getTypeMap().get(entry.getKey());
				
				switch(type) {
				
					case S:
						if (entry.getValue() instanceof String) {
							
							result.put(entry.getKey(), new AttributeValue().withS((String) entry.getValue()));
						}
						else {
							
							throw new DDBTypeMismatchException(entry.getKey(), DDBTypes.S, entry.getValue().getClass());
						}
						
						break;
						
					case SS:
						try {
							
							result.put(entry.getKey(), new AttributeValue().withSS((Collection<String>) entry.getValue()));
						}catch(ClassCastException cce) {
							
							throw new DDBTypeMismatchException(entry.getKey(), DDBTypes.SS, entry.getValue().getClass(), cce);
						}
						
						break;
				
					case N:
						if (entry.getValue() instanceof Integer) {
							
							result.put(entry.getKey(), new AttributeValue().withN(entry.getValue().toString()));
						}
					case NS:
						try {
							
							List<String> convertedList = new ArrayList<String>();
							Collection<Integer> list = (Collection<Integer>) entry.getValue();
							
							for (Integer num : list) {
								
								convertedList.add(num.toString());
							}
							
							result.put(entry.getKey(), new AttributeValue().withNS(convertedList));
						}catch(ClassCastException cce) {
							
							throw new DDBTypeMismatchException(entry.getKey(), DDBTypes.NS, entry.getValue().getClass(), cce);
						}
						
						break;
						
					default:
							
				}
				
			}
		}
		
		return result;
	}	
}
